package com.spzx.channel.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.spzx.channel.domain.vo.IndexVO;
import com.spzx.channel.service.IndexService;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.core.utils.uuid.UUID;
import com.spzx.common.security.annotation.SpzxCache;
import com.spzx.product.api.RemoteBrandService;
import com.spzx.product.api.RemoteCategoryService;
import com.spzx.product.api.RemoteProductService;
import com.spzx.product.api.domain.Brand;
import com.spzx.product.api.domain.Category;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.query.ProductSkuQuery;
import com.spzx.product.api.domain.vo.ProductSkuVO;
import jakarta.annotation.Resource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

@Service
public class IndexServiceImpl implements IndexService {
    @Resource
    RemoteProductService remoteProductService;
    @Resource
    RemoteCategoryService remoteCategoryService;
    @Resource
    RemoteBrandService remoteBrandService;


    /*
        接口安全：
            1、管理员访问的接口
                RBAC权限管理模型控制权限(aop实现)
            2、会员访问的接口
                > 大部分接口不登陆直接可以访问 ： 限流 ip地址黑白名单  用户账户状态
                > 其他接口需要登录后才可以访问
            3、内部远程调用的接口
                > 一般是用户服务 操作数据时远程调用(如果使用权限校验注解，普通会员不能访问)


        步骤：
            1、在product服务中提供接口供给 spzx-channel服务远程调用
            2、在channel服务中 整合openfeign
                    引入依赖
                    配置
                    创建feign客户端
                    业务中装配feign客户端发起远程调用得到数据
     */
    @SpzxCache(prefix = "channel:indexPage",lockExpire = "60")
    @Override
    public IndexVO getIndexPageData() {
        //1、远程调用product服务 获取首页需要的一级分类集合  根据pid=0查询一级分类
        R<List<ProductSku>> listProductSkuR = remoteProductService
                //必须传入内部调用接口 配置的头的值
//                .listIndexSkus(SecurityConstants.INNER);
                .listIndexSkus();
        if(listProductSkuR.getCode() != 200){
            throw new ServiceException("获取热门数据失败");
        }
        //2、远程调用product服务 获取首页需要的热门数据集合  查询sku表 销量最高的8条记录
//        R<List<Category>> listIndexCatesR = remoteCategoryService.listIndexCates(SecurityConstants.INNER);
        R<List<Category>> listIndexCatesR = remoteCategoryService.listIndexCates();
        if(listIndexCatesR.getCode() != 200){
            throw new ServiceException("获取分类数据失败");
        }
        //3、组装首页需要的数据返回
        IndexVO indexVO = new IndexVO();
        //首页需要的分类数据 和 sku数据 都需要哪些属性值
        indexVO.setCategoryList(listIndexCatesR.getData());
        indexVO.setProductSkuList(listProductSkuR.getData());
        return indexVO;

    }
//    @Cacheable(value = "spzx:cache:channel:skupage",key = "#query.pageNo+':'+#query.pageSize+':'+#query.brandId")
    @SpzxCache(prefix = "channel:skuList")
    @Override
    public Page<ProductSku> getSkuList(ProductSkuQuery query) {
        R<Page<ProductSku>> pageR = remoteProductService.listSkus(query);
        if(pageR.getCode()!=200){
            return null;
        }
        return pageR.getData();
    }

    @SpzxCache(prefix = "channel:brandAll")
    @Override
    public List<Brand> getBrandAll() {

        R<List<Brand>> listR = remoteBrandService.listAllBrands();
        return listR.getData();
    }
    /*
        性能优化：
            1、数据库优化
                分库分表
                sql优化： 写法、索引
            2、减少访问数据库的次数
                缓存、异步
       spring提供了自动缓存管理：底层仍然是aop
            步骤：
                1、在需要使用自动缓存管理的项目启动类上添加@EnableCaching
                2、在需要缓存管理的业务方法上(一般是查询方法)使用@Cachable注解(查询时会自动检查缓存 缓存不存在 则查询数据库存到缓存中)
                3、实现spring的 CacheManager接口，提供缓存管理的方法
                    RedisCacheManager: springdataredis包中 提供的实现了缓存管理方法的 管理器




     */
    /*
        aop封装缓存管理+解决缓存穿透雪崩击穿问题的业务逻辑
        缓存管理自定义注解：
            属性1：缓存的key前缀
            属性2：分布式锁的过期时间 单位默认秒
            属性3：是否缓存空值
            属性4：空值的缓存时间 单位秒
            属性5：非空数据的缓存时间 单位秒
        aop通知逻辑： 环绕通知，缓存管理的逻辑是 先查缓存 如果没有命中 再执行目标方法 将返回结果写到缓存中
            1、拼接缓存的key：获取注解的key + 目标方法的参数列表
            2、查询缓存
            3、获取分布式锁：拼接分布式锁的key : 缓存的key+ lock锁的标识
                需要生成uuid 、设置过期时间
                自动续期
            4、再次查询缓存：
                防止先获取到锁的线程执行时，其他线程查询缓存失败 再获取分布式锁的位置自旋
                先进入的线程执行完业务后会将查询的数据缓存到redis，为了避免阻塞的线程再次查询数据库
            5、执行目标方法
                将返回的结果缓存 ，设置过期时间(空值设置较短的过期时间，非空设置较长的过期时间)
            6、释放分布式锁：删除分布式锁(在finally中编写)

            7、对上面的代码进行try catch 如果出现异常 直接执行目标方法 将数据库查询的结果返回 (兜底)
     */
//    @Cacheable
    //查询时先查询缓存 命中返回，未命中执行业务 将结果缓存到redis中
//    @Cacheable(value = "spzx:cache:channel:sku" , key = "#skuId") //value和key会拼接为本次缓存的键
    // skuId=1  , 缓存的键为： spzx:cache:channel:sku::1
//    @CachePut 将返回结果缓存
//    @CacheEvict  驱逐缓存，删除修改的操作可以清除缓存


    @SpzxCache(prefix = "channel:skuById" ,isBloomValidate = true)
    @Override
    public ProductSkuVO getSkuById(Long skuId) {
        //1、先查询缓存：如果存在直接返回
//        Object o = redisTemplate.opsForValue().get("spzx:cache:sku:" + skuId);
//        if(o!=null){
//            return (ProductSkuVO) o;
//        }
        //2、如果缓存未命中：远程调用查询数据
        R<ProductSkuVO> skuVOR = remoteProductService.getSkuDetails(skuId);

        //3、将查询的结果缓存到redis
//        redisTemplate.opsForValue().set("spzx:cache:sku:"+skuId ,
//                skuVOR.getData() , 600 , TimeUnit.SECONDS);

        return skuVOR.getData();
    }


}
